Hydrologi + python

Torodd F. Ottestad

Hydrologiprosjekt i Geofag 1

Datamaterialet er samla inn av NVE og henta frå Sildre.

📌 I det følgjande dømet nyttar me vassføringsdata frå Reinsnosvatn

Målestasjonen ligger i Ullensvang i Vestland og er aktiv. Den har vært i drift siden juni 1917 og leverer i dag 3 forskjellige målinger. (sildre.nve.no)

Prosjektet tek altså utgangspunkt i daglege målingar frå 1917!

Importere bibliotek

Vassføringsdataa er lasta ned i csv-format. Me bruker pandas for å jobba med dette effektivt. I tillegg vert det litt plotting og matematikk, så me treng pyplot og numpy i tillegg.

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

Opprette dataramme

🔍 Dei første linjene

Opprette dataramme

df = pd.read_csv("vassforing_reinsnos_dogn.csv")

Opprette dataramme

df = pd.read_csv(
    "vassforing_reinsnos_dogn.csv", 
    sep = ";", 
    skiprows = 2, 
    names = ["dato", "Vassføring", "Korrigert", "Kontrollert"], 
    parse_dates = True, 
    decimal=","
)

Opprette dataramme

df = pd.read_csv(
    "vassforing_reinsnos_dogn.csv", 
    sep = ";", 
    skiprows = 2, 
    names = ["dato", "Vassføring", "Korrigert", "Kontrollert"], 
    parse_dates = True, 
    decimal=","
)

df.index = pd.to_datetime(df['dato']) 
df.index = pd.to_datetime(df.index.date)
df = df.drop(columns = ["dato", "Korrigert", "Kontrollert"])

Opprette dataramme

Sjekkar dei nyaste fem målingane i tabellen for å sjå at alt ser ok ut.

df.tail()
Vassføring
2023-01-24 1.299365
2023-01-25 1.593457
2023-01-26 2.128418
2023-01-27 2.322153
2023-01-28 2.324195

Opprette dataramme

I tillegg kan me prøva å plotta verdiane i dataramma.
Har med ; for å unngå å visa meldinga <AxesSubplot: >

df.plot();

Middelflom

Middelflommen til ei elv er rekna ut frå den største døgnvassføringa per år over eit lengre tidsrom.

Frå NVE/varsom.no finn me denne definisjonen:

En middelflom (normal flom) defineres som gjennomsnittet av høyeste vannføring hvert år i en lang årrekke.

Middelflom

For å rekna ut dette må me altså først finna den maksimale vassføringa per år. Bruker groupby i Pandas for å finne dette.

årleg_vf_maks = df.groupby(by=[df.index.year]).max()
årleg_vf_maks.head()
Vassføring
1917 26.98031
1918 50.93218
1919 37.72713
1920 63.26006
1921 58.04514

Middelflom

Plottar den nye dataramma årleg_maks som kun har ei måling per år:

årleg_vf_maks.plot();

Middelflom

årleg_vf_maks = årleg_vf_maks.loc[1930:2022]
årleg_vf_maks.head()
Vassføring
1930 43.82251
1931 46.29647
1932 52.52979
1933 68.09634
1934 48.32775

Middelflom

Me vil finna gjennomsnittleg største årlege vassføring over fleire år. Vel her å sjå på eit gjennomsnitt på 30 år om gongen.

maksvf_og_middelflom = årleg_vf_maks.copy()
maksvf_og_middelflom["Middelflom"] = årleg_vf_maks.rolling(window = 30).mean()

Ser på dei første 3 radene

maksvf_og_middelflom.head(3)
Vassføring Middelflom
1930 43.82251 NaN
1931 46.29647 NaN
1932 52.52979 NaN

Middelflom

Ser at dei fem første radene i den nye dataramma er NaN (not a number). Dette skjer fordi ein ikkje kan ta gjennomsnitt av dei siste 30 åra før det har gått 30 år… Sjekkar dei nyaste radene i staden:

maksvf_og_middelflom.tail(3)
Vassføring Middelflom
2020 78.19958 60.860965
2021 46.81937 60.837067
2022 73.51824 60.972971

Middelflom

Plottar dataramma og ser korleis dei nye verdiane ser ut:

maksvf_og_middelflom.plot();

Modellering av middelflom

Ser at trenden er oppadgåande og relativt lineær. Ønsker å laga ein lineær modell. Bruker polyfit frå NumPy.

# åra me har data på middelflom
# frå og med 1959 til men ikkje med 2023
x = np.arange(1959, 2023) 

# finn koeffesientane til førstegradspolynomet
a, b = np.polyfit(x, maksvf_og_middelflom["Middelflom"].loc[1959:2022], 1)

# definerer lineær funksjon
def f(x):
    return a*x + b

Modellering av middelflom

# plottar dataramma
maksvf_og_middelflom.plot(                          
    figsize = (20,9), 
    grid = True, 
    fontsize = 16, 
    linewidth = 3, 
    color = ["lightsteelblue", "royalblue"]
)

# plottar modellen
plt.plot(                                           
    x, f(x), 
    linewidth = 2, 
    label = "Modell for middelflom", 
    color = "red"
)

# pyntar plottet
plt.title("Vassføring og middelflom\n", fontsize = 24)
plt.legend(fontsize = 16)
plt.ylabel("Vassføring, kubikkmeter/s", fontsize = 16)
plt.xlabel("År", fontsize = 16)
#plt.savefig("Reinsnos-årleg maksvassføring-og-middelflom.png", dpi = 300)
plt.show()

Modellering av middelflom

Modellering av middelflom

Ekstrapolasjon - modell for framtidig middelflom

Me kan bruka trendlinja for middelflommen som modell for å seie noko om korleis utviklinga vil vera i åra framover (om utviklinga held fram som no).

Oppgåve

Gjer endring i koden over slik at me ser kva modellen seier om middelflommen fram mot 2100

Modellering av middelflom

Modellering av middelflom

Ekstrapolasjon - modell for framtidig middelflom

# Plottar forlenginga av den lineære modellen
x2 = np.arange(2022, 2101)
plt.plot(x2, f(x2), ":", linewidth = 3, color = "crimson")

Sjekkar opp mot NVE sine flom-data

Frå stasjonsssida til Reinsnosvatn kan me finna data om ulike typar flom.

Flomdata frå NVE

Ser at verdien for 20-årsflom er 78,28 \(m^3/s\). Plottar inn linja \(y=78,28\) i samme figur som tidlegare. Bruker plt.axhline() for å teikna horisontal linje.

Sjekkar opp mot NVE sine flom-data

# plottar 20-årsflom
plt.axhline(
        y = 78.28,
        color = "crimson", 
        linewidth = 3, 
        linestyle = ":", 
        label = "20-årsflom"
)

Sjekkar opp mot NVE sine flom-data

Hydrogram
- vassføring per dag

Startar med å sortera den opprinnelege df til dag i året (day of year - doy). Etterpå grupperer me materialet i ein ny tabell.

Vassføring per dag

df['doy'] = df.index.dayofyear
df['år'] = df.index.year
df.head()
Vassføring doy år
1917-06-15 19.06312 166 1917
1917-06-16 16.46433 167 1917
1917-06-17 16.46433 168 1917
1917-06-18 18.67722 169 1917
1917-06-19 21.97948 170 1917

Vassføring per dag

Lagar ein pivottabell med dag i året som rader og årstal som kolonner

piv = pd.pivot_table(df, index=['doy'],columns=['år'], values=['Vassføring'])
piv.head()

Vassføring per dag

Vassføring
år 1917 1918 1919 1920 1921 1922 1923 1924 1925 1929 ... 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023
doy
1 NaN 0.751369 0.520992 0.110245 0.520992 2.268141 0.858653 NaN 4.303123 NaN ... 4.858096 1.869427 4.191920 28.483190 4.161459 2.347107 12.096400 2.466642 4.124214 2.000038
2 NaN 0.751369 0.481094 0.095431 0.562897 2.268141 0.751369 NaN 4.003652 NaN ... 4.406689 3.295648 3.810454 19.011320 3.759124 2.669421 8.641389 2.288357 5.344579 1.913351
3 NaN 0.701046 0.443164 0.095431 0.606849 2.166990 0.803893 NaN 3.858951 NaN ... 3.952404 4.470398 3.068896 11.995200 3.438255 2.515568 8.580434 2.122319 6.083396 1.827513
4 NaN 0.701046 0.407161 0.095431 0.652886 1.973261 0.915689 NaN 3.717565 NaN ... 3.694488 4.407481 2.748646 8.526679 3.053644 2.336836 7.108252 1.954245 5.640584 1.779285
5 NaN 0.652886 0.373045 0.110245 0.803893 1.703600 0.915689 NaN 3.444615 NaN ... 3.422920 4.161712 2.613774 6.329734 2.739008 2.265773 5.907576 1.806864 4.668677 1.701279

5 rows × 104 columns

Utrekningar

Finn ulike verdiar per dag i året over dei 104 åra.

piv["min"] = piv.min(axis=1)
piv["maks"] = piv.max(axis=1)
piv["75%"] = piv.quantile(q=0.75, axis=1)
piv["median"] = piv.median(axis=1)
piv["25%"] = piv.quantile(q=0.25, axis=1)

piv.head()

Utrekningar

Vassføring min maks 75% median 25%
år 1917 1918 1919 1920 1921 1922 1923 1924 1925 1929 ... 2019 2020 2021 2022 2023
doy
1 NaN 0.751369 0.520992 0.110245 0.520992 2.268141 0.858653 NaN 4.303123 NaN ... 2.347107 12.096400 2.466642 4.124214 2.000038 0.110245 28.48319 2.589138 1.381706 0.701046
2 NaN 0.751369 0.481094 0.095431 0.562897 2.268141 0.751369 NaN 4.003652 NaN ... 2.669421 8.641389 2.288357 5.344579 1.913351 0.095431 19.01132 2.548025 1.417153 0.652886
3 NaN 0.701046 0.443164 0.095431 0.606849 2.166990 0.803893 NaN 3.858951 NaN ... 2.515568 8.580434 2.122319 6.083396 1.827513 0.095431 11.99520 2.491543 1.381060 0.606849
4 NaN 0.701046 0.407161 0.095431 0.652886 1.973261 0.915689 NaN 3.717565 NaN ... 2.336836 7.108252 1.954245 5.640584 1.779285 0.095431 14.41007 2.421806 1.275205 0.652886
5 NaN 0.652886 0.373045 0.110245 0.803893 1.703600 0.915689 NaN 3.444615 NaN ... 2.265773 5.907576 1.806864 4.668677 1.701279 0.110245 18.95604 2.372195 1.207931 0.728653

5 rows × 109 columns

Plottar

# Plottar minimumsverdiar
piv["min"].plot(
    figsize = (20, 8),
    color = "seagreen", 
    linewidth = 0.8, 
    grid = False, 
    fontsize = 18, 
    legend = False,
    label = "Minste verdi"
)

# PLottar maksimumsverdiar
piv["maks"].plot(
    color = "seagreen", 
    linewidth = 0.8, 
    grid = False, 
    fontsize = 18, 
    legend = False,
    label = "Største verdi"
)

# Fargelegg området mellom 25- og 75-persentilane
plt.fill_between(
    piv.index, 
    piv["25%"], piv["75%"], 
    facecolor='green', 
    alpha=0.2, 
    interpolate=True,
    label = "25% - 75%"
)

# Plottar medianen
piv["median"].plot(
    linewidth = 2, 
    color = "darkgreen",
    label = "Median"
)

# Pyntar på plottet
plt.xlabel("Dag i året \n (01. jan - 31. des)", fontsize = 18)
plt.ylabel("Vassføring i $m^3/s$", fontsize = 18)
plt.title("Vassføring - per dag i året \n", fontsize = 24)
plt.legend(fontsize = 16)
plt.savefig("Reinsnos - vassføring per dag gjennom året.png", dpi = 300)
plt.show()

Plottar

Oppgåve

1️⃣ Finn dine eigne elver (ulik storleik, geografisk plassering og type nedbørsfelt).
2️⃣ Lag eigne hydrogram (spesielt den siste figuren) (utan å ha med namn på vassdraget)
3️⃣ Send biletet til Torodd
4️⃣ Diskusjon i klassen

Tips til nedlasting

1️⃣ Finn deg ei elv på sildre.nve.no
2️⃣ Trykk på last ned som csv
3️⃣ Kryss av på aktuelle instillingar
4️⃣ Last ned